<?php
/**
 * Created by jinqiang on 2025/10/15 10:49.
 * Copyright (c) 2025, Your Company. All rights reserved.
 */
/*
// 使用示例
$encoder = new SGTIN96Encoder();
// 示例参数 - 请替换为实际值
$result = $encoder->encode(
    partition: 5,               // 分区方案5：公司前缀24位，商品参考20位
    filter: 'unit',             // 单个商品
    companyPrefix: '123456789012',  // GS1公司前缀
    itemReference: '12345678',      // 商品参考码
    serialNumber: '987654321'       // 序列号
);
if ($result['success']) {
    echo "SGTIN-96生成成功：\n";
    echo "十六进制编码：{$result['hex']}\n";
    echo "EPC URI：{$result['epc_uri']}\n";
} else {
    echo "错误：{$result['error']}\n";
}
*/

namespace App\Http\Repository;


class SGTIN96EncoderRepository
{
    // 头部定义 (8位) - 版本1的SGTIN-96
    const HEADER = '00110000';

    // 滤值定义 (3位)
    const FILTER_VALUES = [
        'all' => '000',       // 所有标签
        'unit' => '001',      // 单个商品
        'case' => '010',      // 箱装商品
        'pallet' => '011',    // 托盘商品
        'inner_pack' => '100' // 内包装
    ];

    // 分区方案定义 (3位) - 决定公司前缀和商品参考码的位数分配
    const PARTITION_SCHEMES = [
        0 => ['company' => 40, 'item' => 12],  // 公司前缀40位，商品参考4位
        1 => ['company' => 37, 'item' => 11],
        2 => ['company' => 34, 'item' => 10],
        3 => ['company' => 30, 'item' => 14],
        4 => ['company' => 29, 'item' => 15],
        5 => ['company' => 27, 'item' => 13],
        6 => ['company' => 20, 'item' => 24],
        7 => ['company' => 28, 'item' => 16]
    ];

    /**
     * 生成SGTIN-96编码
     *
     * @param int $partition 分区方案(0-6)
     * @param string $filter 滤值类型(all/unit/case/pallet/inner_pack)
     * @param string $companyPrefix GS1公司前缀
     * @param string $itemReference 商品参考码
     * @param string $serialNumber 序列号
     * @return array 编码结果
     */
    public function encode($partition, $filter, $companyPrefix, $itemReference, $serialNumber) {
        try {
            // 1. 验证输入参数
            $this->validateParameters($partition, $filter, $companyPrefix, $itemReference, $serialNumber);

            // 2. 获取各部分二进制值
            $binaryFilter = self::FILTER_VALUES[$filter];
            $binaryPartition = $this->decimalToBinary($partition, 3);
            $scheme = self::PARTITION_SCHEMES[$partition];

            $binaryCompany = $this->decimalToBinary($companyPrefix, $scheme['company']);
            $binaryItem = $this->decimalToBinary($itemReference, $scheme['item']);
            $binarySerial = $this->decimalToBinary($serialNumber, 38);  // 序列号固定38位

            // 3. 组合基本二进制数据
            $baseBinary = self::HEADER . $binaryFilter . $binaryPartition .
                $binaryCompany . $binaryItem . $binarySerial;

            // 4. 计算CRC校验码(16位)
            $crcBinary = $this->calculateCRC($baseBinary);

            // 5. 完整二进制和十六进制编码
            //$fullBinary = $baseBinary . $crcBinary;
            $fullBinary = $baseBinary;
            $hexCode = $this->binaryToHex($fullBinary);

            return [
                'code' => 0,
                'structure' => [
                    'header' => ['value' => self::HEADER, 'length' => 8],
                    'filter' => ['value' => $binaryFilter, 'length' => 3, 'type' => $filter],
                    'partition' => ['value' => $binaryPartition, 'length' => 3, 'scheme' => $scheme],
                    'company_prefix' => ['value' => $binaryCompany, 'length' => $scheme['company']],
                    'item_reference' => ['value' => $binaryItem, 'length' => $scheme['item']],
                    'serial_number' => ['value' => $binarySerial, 'length' => 38],
                    'crc' => ['value' => $crcBinary, 'length' => 16]
                ],
                'binary' => $fullBinary,
                'hex' => $hexCode,
                'epc_uri' => "urn:epc:id:sgtin:{$companyPrefix}.{$itemReference}.{$serialNumber}"
            ];
        } catch (\Exception $e) {
            return ['code' => -1, 'msg' => $e->getMessage()];
        }
    }

    /**
     * 验证所有输入参数
     */
    private function validateParameters($partition, $filter, $companyPrefix, $itemReference, $serialNumber) {
        // 验证分区方案
        if (!isset(self::PARTITION_SCHEMES[$partition])) {
            throw new \Exception("无效的分区方案，必须是0-6之间的整数");
        }

        // 验证滤值类型
        if (!isset(self::FILTER_VALUES[$filter])) {
            throw new \Exception("无效的滤值类型，必须是: " . implode(', ', array_keys(self::FILTER_VALUES)));
        }

        // 验证公司前缀和商品参考码
        $scheme = self::PARTITION_SCHEMES[$partition];
        $maxCompany = bindec(str_repeat('1', $scheme['company']));
        $maxItem = bindec(str_repeat('1', $scheme['item']));
        $maxSerial = bindec(str_repeat('1', 38));

        if (!is_numeric($companyPrefix) || $companyPrefix < 0 || $companyPrefix > $maxCompany) {
            throw new \Exception("公司前缀必须是0到{$maxCompany}之间的整数 ({$scheme['company']}位二进制)");
        }

        if (!is_numeric($itemReference) || $itemReference < 0 || $itemReference > $maxItem) {
            throw new \Exception("商品参考码必须是0到{$maxItem}之间的整数 ({$scheme['item']}位二进制)");
        }

        if (!is_numeric($serialNumber) || $serialNumber < 0 || $serialNumber > $maxSerial) {
            throw new \Exception("序列号必须是0到{$maxSerial}之间的整数 (38位二进制)");
        }
    }

    /**
     * 十进制转指定长度的二进制
     */
    private function decimalToBinary($decimal, $length) {
        $binary = decbin($decimal);
        if (strlen($binary) > $length) {
            throw new \Exception("数值超出{$length}位二进制的表示范围");
        }
        return str_pad($binary, $length, '0', STR_PAD_LEFT);
    }

    /**
     * 二进制转十六进制
     */
    private function binaryToHex($binary) {
        $hex = '';
        $binary = str_pad($binary, ceil(strlen($binary)/4)*4, '0', STR_PAD_LEFT);

        for ($i = 0; $i < strlen($binary); $i += 4) {
            $chunk = substr($binary, $i, 4);
            $hex .= str_pad(dechex(bindec($chunk)), 1, '0', STR_PAD_LEFT);
        }

        return strtoupper($hex);
    }

    /**
     * 计算CRC16校验码
     */
    private function calculateCRC($binary) {
        $crc = 0xFFFF;
        $poly = 0x8005;

        for ($i = 0; $i < strlen($binary); $i++) {
            $bit = (int)substr($binary, $i, 1);
            $crc ^= ($bit << 15);

            if ($crc & 0x8000) {
                $crc = ($crc << 1) ^ $poly;
            } else {
                $crc <<= 1;
            }

            $crc &= 0xFFFF;
        }

        return str_pad(decbin($crc), 16, '0', STR_PAD_LEFT);
    }
}
